package net.sunniwell.flowview.view;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.support.v4.view.ViewConfigurationCompat;
import android.util.AttributeSet;
import android.util.Log;
import android.util.SparseArray;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.widget.Button;
import android.widget.Scroller;

import net.sunniwell.flowview.R;

import java.util.ArrayList;

import static android.support.v4.widget.ViewDragHelper.INVALID_POINTER;

/**
 * 自定义流式布局
 * 自定义布局的主要流程还是
 * 1. onMeasure  根据自身的MeasureSpec 结合子View 的layoutParams，同时根据自身的View排列规则、
 * 确定ziView的测量参数。 可以使用{@link ViewGroup#measureChild(View, int, int)} 来确定，但是某些特殊的情况还是自己处理比较好。
 * 2： onLayout ，针对子View测量完成之后，根据自己的布局规则，给每个子VIew确定位置。这里还要注意每一个                       View 的显示情况
 * <p>
 * 滑动事件测处理。
 */
public class SWFlowView2 extends ViewGroup {

    private static final String TAG = "SWFlowView";
    private SparseArray<ArrayList<View>> mViews = new SparseArray<>();
    private float lineSpacing = 0;
    private int spaceColor;


    private float mLastX = -1, mLastY = -1;



    /**
     * 用来记录down事件的初始化位置，如果滑动距离超过了mTouchSlop,那么就判定为滑动事件
     */
    private float initialDownX, initialDownY;


    // 2.完成跟踪手势滑动的功能之后还是需要进行可否滑动的限制，
    // 如果当前Viewgroup的内容没有超出当前的大小，那么就不能进行滑动。
    //如果ViewGroup 内容超出了测量时大小，可以设置滑动。
    //  3： 限制滑动的距离，如果滑动超出边界，那么就不可以滑动

    /**
     * 用来标记当前View是否可以进行滑动的标志位，如果可以滑动则为True。
     * 目前的FlowView仅仅可以进行垂直滑动，不支持水平滑动。
     */
    private boolean scrollAble = false;


    // 4 增加Scroller 进行滑动控制，实现比较愉悦的滑动体验
    private Scroller mScroller;

    // 使用VelocityTracker进行fling
    private VelocityTracker mVelocityTracker;
    private int mActivePointerId = INVALID_POINTER;
    private int mMinimumVelocity;
    private int mMaximumVelocity;
    private float  mVerticalScrollFactor;


    private int mHeightMeasuredByParent;
    private int mHeightMeasuredByChild;


    private int mTouchSlop; // 用来判断是否是一次滑动，如果大于这个值就是滑动


    public SWFlowView2(Context context) {
        this(context, null);
    }

    public SWFlowView2(Context context, AttributeSet attrs) {
        this(context, attrs, -1);
    }

    public SWFlowView2(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initViewParameter(context, attrs);


    }


    private void initViewParameter(Context context, AttributeSet attrs) {
        mScroller = new Scroller(context);

        mVelocityTracker = VelocityTracker.obtain();

        //  获取当前的配置参数，用来确定滑动了多长算一次就需要处理move事件了。
        ViewConfiguration configuration = ViewConfiguration.get(context);
        mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(configuration);
        mTouchSlop = configuration.getScaledTouchSlop();
        mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
        mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();

        // t
        if (attrs == null) {
            return;
        }

        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.flowview);
        lineSpacing = array.getDimensionPixelSize(R.styleable.flowview_deviderheight, 0);
        spaceColor = array.getColor(R.styleable.flowview_devidercolor, Color.parseColor("#33ff0000"));

        array.recycle();
        Log.d(TAG, "initViewParameter:  lineSpace:" + lineSpacing);
    }


    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        int count = getChildCount();
        int widhtMode = MeasureSpec.getMode(widthMeasureSpec);
        int widhtSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getSize(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        mHeightMeasuredByParent = heightSize;

        int paddingLeft = getPaddingLeft();
        int paddingRight = getPaddingRight();
        int paddingTop = getPaddingTop();
        int paddingBottom = getPaddingBottom();

        // step 1. 针对每一个child进行measure
        int line = 0;
        int lineRemain;
        int measuredViewHeight;
        mViews.clear();
        for (int i = 0; i < count; i++) {
            Log.d(TAG, "onMeasure:------------  i:" + i);
            View child = getChildAt(i);
            if (mViews.size() == 0) {
                Log.d(TAG, "onMeasure: firstLine");
                mViews.put(line, new ArrayList<View>());
                lineRemain = widhtSize;
            }
            if (child.getVisibility() == View.GONE) {
                Log.d(TAG, "onMeasure: current View is Gone");
                break;
            }


            if (mViews.get(line).size() == 0) {
                Log.d(TAG, "onMeasure: current is newLine");
                //measureChildInNewLine(child, widthMeasureSpec,heightMeasureSpec);

                LayoutParams layoutParams = child.getLayoutParams();
                int childWidthMeasureSpec = getChildMeasureSpecInternal(widthMeasureSpec, paddingLeft + paddingRight, layoutParams.width, false);
                int childHeightMeasureSpec = getChildMeasureSpecInternal(heightMeasureSpec, paddingTop + paddingBottom, layoutParams.height, true);

                child.measure(childWidthMeasureSpec, childHeightMeasureSpec);

                mViews.get(line).add(child);


            } else {
                Log.d(TAG, "onMeasure: lineHasOtherView ,consider resize remainView,or createNewLine");
                //  如果这一行有了View，那么就需要重新移动View到下一行了。
                ArrayList<View> lineViews = mViews.get(line);
                int linViewCount = lineViews.size();
                int widthUsed = getPaddingLeft() + getPaddingRight();
                if (linViewCount > 0) {
                    for (int j = 0; j < linViewCount; j++) {
                        widthUsed += lineViews.get(j).getMeasuredWidth();
                    }
                }
                Log.d(TAG, "onMeasure:  widthUsed:" + widthUsed);
                if (child.getLayoutParams().width > 0) {
                    // child width is specified
                    Log.d(TAG, "onMeasure:  layoutParameter siez seccified.");
                    if ((widthUsed + child.getLayoutParams().width) > widhtSize || child.getLayoutParams().width == LayoutParams.MATCH_PARENT) {
                        Log.d(TAG, "onMeasure: line++");
                        widthUsed = getPaddingStart() + getPaddingEnd();
                        line++;
                        mViews.put(line, new ArrayList<View>());


                    }
                    LayoutParams layoutParams = child.getLayoutParams();
                    int childWidthMeasureSpec = getChildMeasureSpecInternal(widthMeasureSpec, widthUsed, layoutParams.width, false);
                    int childHeightMeasureSpec = getChildMeasureSpecInternal(heightMeasureSpec, paddingTop + paddingBottom, layoutParams.height, true);
                    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);


                    mViews.get(line).add(child);
                }

 /*               else if (child.getLayoutParams().width == LayoutParams.MATCH_PARENT) {
                    widthUsed = getPaddingStart() + getPaddingEnd();
                    line++;
                    mViews.put(line, new ArrayList<View>());

                    LayoutParams layoutParams = child.getLayoutParams();
                    int childWidthMeasureSpec = getChildMeasureSpecInternal(widthMeasureSpec, widthUsed, layoutParams.width, false);
                    int childHeightMeasureSpec = getChildMeasureSpecInternal(heightMeasureSpec, paddingTop + paddingBottom, layoutParams.height, true);
                    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);


                    mViews.get(line).add(child);

                }*/
                else {
                    Log.d(TAG, "onMeasure: otherSituations");
                    if (widthUsed >= widhtSize) {
                        Log.d(TAG, "onMeasure:  newLine ");
                        widthUsed = getPaddingStart() + getPaddingEnd();
                        line++;
                        mViews.put(line, new ArrayList<View>());
                    }

                    //TODO  处理其他情况
                    LayoutParams layoutParams = child.getLayoutParams();
                    int childWidthMeasureSpec = getChildMeasureSpecInternal(widthMeasureSpec, widthUsed, layoutParams.width, false);
                    int childHeightMeasureSpec = getChildMeasureSpecInternal(heightMeasureSpec, paddingTop + paddingBottom, layoutParams.height, true);
                    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
                    mViews.get(line).add(child);
                }


            }


        }

        Log.d(TAG, "onMeasure:  child measure done:" + widhtSize + ";heightsize:" + heightSize);

        int lineCountInPage = mViews.size();
        int heightToBeSet = 0;
        for (int i = 0; i < lineCountInPage; i++) {
            Log.d(TAG, "onMeasure: current is Line:" + i);
            StringBuilder sb = new StringBuilder();
            sb.append("lineStart: ");
            ArrayList<View> views = mViews.get(i);
            int maxHeight = 0;
            int countInOneLine = views.size();
            for (int j = 0; j < countInOneLine; j++) {
                Button childView = (Button) views.get(j);
                sb.append("\t (" + childView.getMeasuredWidth() + "," + childView.getMeasuredHeight() + childView.getText() + ")");
                maxHeight = Math.max(maxHeight, childView.getMeasuredHeight());

            }
            Log.d(TAG, sb.toString());
            heightToBeSet += maxHeight + lineSpacing;
        }
        // 添加 lineSpacec

        Log.d(TAG, "onMeasure: heightToBeSet:" + heightToBeSet);
        mHeightMeasuredByChild = (int) (heightToBeSet - lineSpacing);
        if (mHeightMeasuredByChild > mHeightMeasuredByParent) {
            scrollAble = true;
            Log.d(TAG, "onMeasure: maxScrolly:" + (heightToBeSet - heightSize) + ";heightMeasureedBy" + mHeightMeasuredByParent + ";toBeSet:" + mHeightMeasuredByChild);
        }

        setMeasuredDimension(widhtSize, Math.max(mHeightMeasuredByChild, heightSize));


    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {


        int lineCountInPage = mViews.size();
        int lineTop = t;
        for (int i = 0; i < lineCountInPage; i++) {

            int lineStart = getPaddingStart();
            int maxHeight = 0;

            ArrayList<View> viewsOfOneLine = mViews.get(i);
            int viewCountOnLine = viewsOfOneLine.size();
            for (int j = 0; j < viewCountOnLine; j++) {
                View itemView = viewsOfOneLine.get(j);
                itemView.layout(l + lineStart, lineTop, l + lineStart + itemView.getMeasuredWidth(), lineTop + itemView.getMeasuredHeight());
                maxHeight = Math.max(maxHeight, itemView.getMeasuredHeight());
                lineStart += itemView.getMeasuredWidth();
            }
            lineTop += maxHeight + lineSpacing;

        }


    }


    public static int getChildMeasureSpecInternal(int spec, int padding, int childDimension, boolean isHeight) {
        int specMode = MeasureSpec.getMode(spec);
        int specSize = MeasureSpec.getSize(spec);
        Log.d(TAG, "getChild :" + specSize + ";modie:" + specMode + "; padding" + padding + ";chDimension:" + childDimension);

        int size = Math.max(0, specSize - padding);
        Log.d(TAG, "getChildl: remainSize:" + size);

        int resultSize = 0;
        int resultMode = 0;

        switch (specMode) {
            // Parent has imposed an exact size on us
            case MeasureSpec.EXACTLY:
                Log.d(TAG, " exact");
                if (childDimension >= 0) {
                    if (isHeight) {
                        resultSize = childDimension;
                    } else {
                        resultSize = Math.min(childDimension, size);
                    }


                    resultMode = MeasureSpec.EXACTLY;
                } else if (childDimension == LayoutParams.MATCH_PARENT) {
                    // Child wants to be our size. So be it.
                    if (isHeight) {
                        // 限制最大高度
                        resultSize = Math.max(size, 200);
                        resultMode = MeasureSpec.AT_MOST;
                    } else {
                        resultSize = size;
                        resultMode = MeasureSpec.EXACTLY;
                    }

                } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                    // Child wants to determine its own size. It can't be
                    // bigger than us.
                    if (isHeight) {
                        resultSize = size;
                        resultMode = MeasureSpec.AT_MOST;
                    } else {
                        resultSize = size;
                        resultMode = MeasureSpec.AT_MOST;
                    }

                }
                break;

            // Parent has imposed a maximum size on us
            case MeasureSpec.AT_MOST:
                Log.d(TAG, "   most ");
                if (childDimension >= 0) {
                    // Child wants a specific size... so be it
                    resultSize = childDimension;
                    resultMode = MeasureSpec.EXACTLY;
                } else if (childDimension == LayoutParams.MATCH_PARENT) {
                    // Child wants to be our size, but our size is not fixed.
                    // Constrain child to not be bigger than us.

                    if (isHeight) {
                        // 限制最大高度
                        resultSize = (size > 200 ? 200 : size);
                        resultMode = MeasureSpec.AT_MOST;
                    } else {
                        resultSize = size;
                        resultMode = MeasureSpec.EXACTLY;
                    }
                   /* resultSize = size;
                    resultMode = MeasureSpec.AT_MOST*/
                    ;
                } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                    // Child wants to determine its own size. It can't be
                    // bigger than us.
                    resultSize = size;
                    resultMode = MeasureSpec.AT_MOST;
                }
                break;

            // Parent asked to see how big we want to be
            case MeasureSpec.UNSPECIFIED:
                Log.d(TAG, " unspec");
                if (childDimension >= 0) {
                    // Child wants a specific size... let him have it
                    resultSize = childDimension;
                    resultMode = MeasureSpec.EXACTLY;
                } else if (childDimension == LayoutParams.MATCH_PARENT) {
                    // Child wants to be our size... find out how big it should
                    // be
                    resultSize = size;
                    resultMode = MeasureSpec.UNSPECIFIED;
                } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                    // Child wants to determine its own size.... find out how
                    // big it should be
                    resultSize = size;
                    resultMode = MeasureSpec.UNSPECIFIED;
                }
                break;
        }
        //noinspection ResourceType
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }


    private void resetIntercept() {

    }

    public void resetMove() {
        mLastX = mLastY = -1;
    }


    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.d(TAG, "dispatchTouchEvent: " + ev);
        return super.dispatchTouchEvent(ev);
    }

    /**
     * 判断touch事件是否需要进行拦截，
     * 这里的判断条件就是
     * 1： 如果是滑动事件，并且滑动事件的滑动长度超过一定的距离那么就拦截滑动，自己处理滑动事件。
     * https://wanandroid.com/wxarticle/chapters/json
     *
     * @param ev
     * @return
     */
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        int action = ev.getAction();
        Log.d(TAG, "onInterceptTouchEvent: " + action);


        switch (action) {
            // 在move事件时进行处理拦截， 一旦移动的距离超过这个临界值，如果当前界面需要滑动，那么就要求
            //当前界面跟着手指移动方向进行滑动。
            // TODO 后期处理的时候可以考虑将mTouchSlop加入到滑动距离当中。
            case MotionEvent.ACTION_MOVE:
                float diffY = Math.abs(ev.getY() - initialDownY);
                Log.d(TAG, "sloap:" + mTouchSlop + ";currentDiff:" + diffY + "; initY:" + initialDownY);
                if (diffY > mTouchSlop) {
                    Log.d(TAG, "onInterceptTouchEvent: " + ev.getY());
                    //在这里将初始落点设置为 当前的点是为了保证后面使用这个值的时候不至于产生延迟滑动
                    // 看
                    initialDownY = ev.getY();
                    initialDownX = ev.getX();
                    mVelocityTracker.addMovement(ev);
                    final ViewParent parent = getParent();
                    if (parent != null) {
                        parent.requestDisallowInterceptTouchEvent(true);
                    }
                    return true;
                }


                break;
            case MotionEvent.ACTION_DOWN:
                // 如果是interceptdown，初始化判定位置。
                Log.d(TAG, "onInterceptTouchEvent:  actiondown ,");
                initialDownX = ev.getX();
                initialDownY = ev.getY();
                // 如果是Down事件，那么主动停止Scroll滑动
                if (!mScroller.isFinished()) {
                    mScroller.abortAnimation();
                }

                mVelocityTracker.addMovement(ev);
                return false;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                Log.d(TAG, "onInterceptTouchEvent: actionCancel");
                initialDownX = -1;
                initialDownY = -1;
                break;

        }


        return super.onInterceptTouchEvent(ev);
    }


    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.d(TAG, "onTouchEvent: " + event.getAction());
        if (!scrollAble) {
            return false;
        }

        int action = event.getAction();
        switch (action) {
            case MotionEvent.ACTION_MOVE:

                if (!mScroller.isFinished()) {
                    mScroller.abortAnimation();
                }
                int diffY = (int) (event.getY() - initialDownY);
                // TODO 使用Scroller进行协助滑动 可以让滑动进行的更流畅

                int deltaY = diffY;

                int scrollY = getScrollY();
                int scrollRange = mHeightMeasuredByChild - mHeightMeasuredByParent;
                Log.d(TAG, "onTouchEvent: ScrollY:" + scrollY + ";diff:" + (diffY) + ";range:" + scrollRange);


                if (scrollY - diffY > scrollRange) {
                    scrollY = scrollRange;
                } else if (scrollY - diffY < 0) {
                    scrollY = 0;
                } else {
                    scrollY -= diffY;
                }

                if (scrollY - diffY > scrollRange) {
                    deltaY = scrollRange - scrollY;
                } else if (scrollY - diffY < 0) {
                    deltaY = -scrollY;
                }
                Log.d(TAG, "onTouchEvent: deltaY:" + deltaY);
                mScroller.startScroll(0, scrollY, 0, -deltaY);

                Log.d(TAG, "onTouchEvent: diff:" + (scrollY - getScrollY()));
                //scrollTo(0, scrollY);
                //TODO 使用Scroller辅助滑动类进行滑动

                //startScroll 方法仅仅是记录这个滑动过程的各个参数，起始滑动地点，以及需要滑动的距离
                //mScroller.startScroll(mScroller.getFinalX(), mScroller.getFinalY(), 0, (scrollY - getScrollY()));
                invalidate(); // 调用inValidate方法来触发滑动绘制
                //scrollBy(0, -diffY);
                initialDownX = event.getX();
                initialDownY = event.getY();

                break;
            case MotionEvent.ACTION_DOWN:
                // 如果是interceptdown，初始化判定位置。
                Log.d(TAG, "onTouchEvent:  actiondown ,");
                initialDownX = event.getX();
                initialDownY = event.getY();
                if (!mScroller.isFinished()) {
                    mScroller.abortAnimation();
                }
                mActivePointerId = event.getPointerId(0);
                //startNestedScroll(SCROLL_AXIS_HORIZONTAL);
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                Log.d(TAG, "onTouchEvent: actionUp");
                initialDownX = -1;
                initialDownY = -1;

                final VelocityTracker velocityTracker = mVelocityTracker;
                velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
                int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);
                Log.d(TAG, "onTouchEvent:  initialVelocity:"+ initialVelocity+";min:"+ mMinimumVelocity);

                if ((Math.abs(initialVelocity) > mMinimumVelocity)) {
                    flingWithNestedDispatch(-initialVelocity);
                }

                mActivePointerId = INVALID_POINTER;


                break;

        }
        if (mVelocityTracker != null) {
            mVelocityTracker.addMovement(event);
        }

        return true;
    }


    private void flingWithNestedDispatch(int velocityY) {


                fling(velocityY);


    }
    public void fling(int velocityY) {
        if (getChildCount() > 0) {
            int height = getHeight() - getPaddingBottom() - getPaddingBottom();
            int bottom = getChildAt(0).getHeight();

            // 计算 最大的滑动距离Y
            int scrollRange = mHeightMeasuredByChild - mHeightMeasuredByParent;
            mScroller.fling(getScrollX(), getScrollY(), 0, velocityY, 0, 0, 0,
                    scrollRange);



            postInvalidateOnAnimation();
        }
    }



    @Override
    public void computeScroll() {
        // mScroller.computeScrolloffset 如果为true表示滑动没有结束
        if (mScroller.computeScrollOffset()) {
            Log.d(TAG, "computeScroll: x:" + mScroller.getCurrX() + ";y:" + mScroller.getCurrY());
            scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
            invalidate();

        }
    }
}
